//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "XYmdm.h"

#define U_COM 0

#if U_COM
#define CONCOM(x) x
#else
#define CONCOM(x)
#endif

//EXTERN_C void hexouts(unsigned char* buf, UINT buflen);
int g_IsXmodem1k; /* 1k blocks supported */
int g_IsYmodem;
int g_IsUseCrc; /* receiver wants crc */

unsigned int g_State;
int     g_Protocol;
int     g_XmTimeout; /* timeout, seconds */

int  g_ErrorCount, g_ErrorCount2, g_IgnoreCount;
int  g_EotCount; /* count EOT's, reject first one */
int  g_InCount;   /* characters received this packet */
int  g_PktLen; /* length of this format block  */
int     g_DataLen;  /* length of this packet data */
int  g_PktHdrLen; /* id, cmpl, checksum or crc */
int  g_PacketId ; /* id of last received packet */
int  g_PacketCount ; /* # packets received */

void XYmodemInit()
{
    g_IsXmodem1k = 1; /* 1k blocks supported */
    g_IsYmodem = 1;
    g_IsUseCrc = 1; /* receiver wants crc */

    g_State = X_START;
    g_Protocol = YMDM;
    g_XmTimeout = INITTO; /* timeout, seconds */

    g_ErrorCount = g_ErrorCount2 = g_IgnoreCount = 0;
    g_EotCount = 0; /* count EOT's, reject first one */
    g_InCount = 0;   /* characters received this packet */
    g_PktLen = MAXPACKET; /* length of this packet data */
    g_PktHdrLen = (PKGHDR + CRCLEN); /* id, cmpl, checksum or crc */
    g_PacketId = 255; /* id of last received packet */
    g_PacketCount = 0; /* # packets received */
}

//return : -1 error ; other : packet status
int RECV(char* PacketBuf, int acstatus)
{
    int err = 0;
    int pkstatus = PK_NONE;

    if (X_START == g_State) {
        return PK_CANCEL;
    }

    if (acstatus) {
        switch (acstatus) {
            case AC_NONE:
                break;

            case AC_REJECT:
                RECV_REJECT();
                break;

            case AC_ACCEPT:
                RECV_ACCEPT();
                break;

            case AC_YMD1STPK:
                g_PacketCount = 1;
                RECV_ACCEPT();
                if (g_Protocol != YMDMG) {
                    Flush('C');
                }
                else {
                    Flush('G');
                }
                break;

            case AC_CANCEL:
                Cancel();
                return PK_CANCEL;

            default:
                CONCOM(printk("NO ACTION STATUS!\n"));
                break;
        }
    }

    err = RECV_RECV(PacketBuf);
    if (XmFullBlk == err) {
        CONCOM(printk("Receive a full packet!\n"));
        err = RECV_PACKET(PacketBuf, &pkstatus);
        if ((0 == err) && (PK_VALIDDATA == pkstatus)) {
            CONCOM(printk("VALID DATA PACKET!\n"));
            /* is this the first packet? */
            if (g_PacketCount == 0) {
                if (g_IsYmodem) {
                    CONCOM(printk("first packet file info!\n"));
                    pkstatus = PK_FILEINFO;
                    return pkstatus;
                }
            }
            ++g_PacketCount;
            return pkstatus;
        }
        else if (XmErrSequence == err) {
            return PK_CANCEL;
        }
    }
    else {
        CONCOM(printk("Receive packet error code : %08x!\n", err));
        return PK_CANCEL;
    }
    return pkstatus;
}

int RECV_RECV(char* PacketBuf)
{
    int err, rv = 0;
    char *ptr = PacketBuf;
    char c;

    do {
        err = XmErrNone;
        rv = RecvStr(ptr, g_PktLen, g_XmTimeout);
        CONCOM(printk("RECV STRING LENGTH %d Char %x\n", rv, ptr[0]));
        if (rv) {
            if ((X_INIT == g_State) || (X_WAIT == g_State)) {
                c = ptr[0];
                switch (c) {
                    case SOH :
                    case STX :
                        g_DataLen = (c == STX) ? MAXPACKET : MINPACKET ;
                        g_PktLen = g_DataLen + g_PktHdrLen;
                        g_InCount = 0;
                        g_State = X_PACKET;
                        g_XmTimeout = PKTTO;
                        CONCOM(printk("X_INIT STX/SOH %d BLK LEN %d\n", g_DataLen, g_PktLen));
                        break;

                    case EOT :
                        CONCOM(printk("EOT\n"));
                        /* Remote should send twice to EOT declare Transfer finished */
                        if (++g_EotCount > 1) {
                            Flush(ACK);
                            if (g_IsYmodem) {
                                /* restart protocol */
                                RECV_INIT(g_Protocol);
                                //return XmNewFile;
                            }
                            else {
                                return XmDone;
                            }
                        }
                        else {
                            RECV_REJECT(); /* make xmitter try again */
                        }
                        break;

                    case CAN :
                        return XmErrCancel;

                    default :  /* ignore all others */
                        CONCOM(printk("g_IgnoreCount:"));
                        Cancel();
                        return XmErrIgnore;
                }/*end switch(c)*/
            }
            err = XmErrNone;

            if (X_PACKET == g_State) {
                ptr += rv;
                g_InCount += rv;
                if (g_PktLen == g_InCount) {
                    err = XmFullBlk;
                }
            }
        }
        else {
            //TO DO: TIME OUT
            CONCOM(printk("RECV TIMEOUT:"));
            switch (g_State) {
                case X_INIT :
                    CONCOM(printk("<X_INIT>:"));
                    if (++g_ErrorCount > MAXERROR) {
                        CONCOM(printk("MAXERROR\n"));
                        err = XmErrInitTo;
                    }
                    else {
                        CONCOM(printk("RESTART\n"));
                        RECV_START(g_Protocol);
                    }
                    break;

                case X_WAIT :   /* timeout while waiting */
                    CONCOM(printk("<X_WAIT>\n"));
                case X_PACKET :   /* timeout in mid packet */
                    CONCOM(printk("<X_PACKET>\n"));
                    if (++g_ErrorCount > MAXERROR) {
                        Cancel();
                        err = XmErrRcvTo;
                    }
                    else {
                        if (YMDMG != g_Protocol) {
                            RECV_REJECT();
                            ptr = PacketBuf;
                            g_InCount = 0;
                        }
                        else {
                            Flush('G');
                            err = XmErrRcvTo;
                        }
                    }
                    break;

                default:
                    CONCOM(printk("<default>\n"));
                    Cancel();
                    err = XmErrInt;
            }
        }
    } while (XmErrNone == err);

    return err;
}

/* send startup character */
int RECV_START(int protocol)
{
    char c;
    switch (protocol) {
        case XMDM :
            c = NAK;
            g_IsUseCrc = 0;
            g_XmTimeout = INITTO;
            break;

        case XMDMCRC :
            c = 'C';
            g_IsUseCrc = 1;
            g_XmTimeout = INITTO2;
            break;

        case YMDM :
            c = 'C';
            g_XmTimeout = INITTO;
            break;

        case YMDMG :
            c = 'G';
            g_XmTimeout = INITTO;
            break;

        default:
            g_Protocol = XMDM;
            c = NAK;
            g_IsUseCrc = 0;
            g_XmTimeout = INITTO;
    }

    return Flush(c);
}

int RECV_PACKET(char* Packet, int* pkstatus)
{
//    hexouts((unsigned char*)Packet, g_PktLen);
    int id  = (int)Packet[1];
    int idc = (int)Packet[2];
    int csum = 0;

    if (idc != (255 - id)) {
        *pkstatus = PK_REJECT;
        CONCOM(printk("RECV_REJECT idc err\n"));
        return RECV_REJECT();
    }
    if (id == g_PacketId) {  /* duplicate */
        *pkstatus = PK_DUPLICATE;
        CONCOM(printk("RECV_ACCEPT duplicate\n"));
        return RECV_ACCEPT();
    }

    if (id != ((g_PacketId + 1) % 256)) { /* out of sequence */
        *pkstatus = PK_CANCEL;
        Cancel();
        CONCOM(printk("RECV_CANCEL out of sequence\n"));
        return XmErrSequence;
    }

    if (g_IsUseCrc) {
        int crc0 = ((unsigned char)Packet[g_DataLen + PKGHDR] << 8) | ((unsigned char)Packet[g_DataLen + PKGHDR + 1]);
        int crc1 = CalCrc(Packet + PKGHDR, g_DataLen);
        if (crc0 != crc1) {
            *pkstatus = PK_REJECT;
            CONCOM(printk("RECV_REJECT CRC err\n"));
            return RECV_REJECT();
        }
    }
    else {
        /* compute checksum */
        csum = CalcChecksum(Packet + PKGHDR, g_DataLen);
        if (csum != (unsigned char)Packet[PKGHDR + g_DataLen]) {
            *pkstatus = PK_REJECT;
            CONCOM(printk("RECV_REJECT CheckSum err\n"));
            return RECV_REJECT();
        }
    }
    /* it's a good packet */
    g_PacketId = (g_PacketId + 1) % 256;
    *pkstatus = PK_VALIDDATA;
    return 0;
}

int RECV_REJECT()
{
    g_State = X_WAIT;
    g_XmTimeout = INITTO ;
    return Flush(NAK) ;
}

int RECV_ACCEPT()
{
    g_State = X_WAIT;
    g_XmTimeout = INITTO;
    if (g_Protocol != YMDMG)
        return Flush(ACK);
    return 0;
}

int RECV_INIT(int protocol)
{
    int err;
    g_State = X_START;
    g_Protocol = protocol;
    if ((YMDM == g_Protocol) || (YMDMG == g_Protocol)) {
        g_IsYmodem = 1;
    }
    else {
        g_IsYmodem = 0;
        if (XMDM == g_Protocol) {
            g_IsUseCrc = 0;
        }
    }

    g_EotCount = g_ErrorCount = g_ErrorCount2 = 0;

    err = RECV_START(g_Protocol);
    if (err)
        return err;

    g_State = X_INIT;
    g_PacketId = g_IsYmodem ? 255 : 0;
    g_PktHdrLen = (XMDM == g_Protocol) ? PKGHDR + CSMLEN : PKGHDR + CRCLEN;
    g_PacketCount = 0;
    CONCOM(printk("RECV INTI .......... \n"));
    return XmErrNone ;
}

int XMIT(char *packet, int pkstatus, int xmitl)
{
    int rv, ac_status = 0;

    if ((PK_FILEINFO == pkstatus) || (PK_EOT == pkstatus)) {
        g_PacketId = 0;
        XMIT_BUILD(packet, xmitl);
//        hexouts((unsigned char*)packet, g_PktLen);

        ac_status = XMIT_LISTEN();
        if (AC_DATA == ac_status) {
            rv = XMIT_XMIT(packet);
            if (AC_CANCEL == rv) {
                CONCOM(printk("XMIT PK_FILEINFO A\n"));
                return rv;
            }

            ac_status = XMIT_LISTEN();
            switch (ac_status) {
                case AC_ACCEPT:
                    if (PK_EOT == pkstatus) {
                        ac_status = AC_FILEINFO;
                        break;
                    }
                    ac_status = XMIT_LISTEN();
                    if (AC_DATA == ac_status) {
                        ac_status = AC_FILEINFO;
                        CONCOM(printk("XMIT PK_FILEINFO B\n"));
                    }

                    if (AC_DATA != ac_status) {
                        break;
                    }

                case AC_DATA:
                    ac_status = AC_FILEINFO;//batch transfer end
                    CONCOM(printk("XMIT PK_FILEINFO C\n"));
            }
//            if (AC_ACCEPT == ac_status) {
//                if (xmitl) {//a new file transfer
//                    ac_status = XMIT_LISTEN();
//                    if (AC_DATA == ac_status) {
//                        ac_status = AC_FILEINFO;
//                        CONCOM(printk("XMIT PK_FILEINFO B\n"));
//                    }
//                }
//                else {
//                    ac_status = AC_FILEINFO;//batch transfer end
//                    CONCOM(printk("XMIT PK_FILEINFO C\n"));
//                }
//            }
//            else if (AC_DATA == ac_status) {
//                ac_status = AC_FILEINFO;
//                CONCOM(printk("XMIT PK_FILEINFO D\n"));
//            }
        }
        if (AC_FILEINFO != ac_status) {
            CONCOM(printk("XMIT PK_FILEINFO E\n"));
            ac_status = AC_TIMEOUT;
        }
    }
    else if (PK_VALIDDATA == pkstatus) {//DATA TRANSFER

        XMIT_BUILD(packet, xmitl);
//        hexouts((unsigned char*)packet, g_PktLen);

        do {
            rv = XMIT_XMIT(packet);
            if (AC_CANCEL == rv) {
                CONCOM(printk("XMIT PK_VALIDDATA A\n"));
                return rv;
            }
            ac_status = XMIT_LISTEN();
            CONCOM(printk("DATA TRANSFER [%08x]\n", ac_status));
        } while (AC_REJECT == ac_status);
    }

    if (X_EOT == g_State) {
        do {
            SendChar(EOT);
            ac_status = XMIT_LISTEN();
        } while (AC_REJECT == ac_status);
        ac_status = AC_EOT;
        g_State = X_START;
    }
    return ac_status;
}

//return val : remote response
int XMIT_LISTEN()
{
    int re_acs = AC_TIMEOUT, rv;
    char res_c;

    rv = RecvStr(&res_c, 1, g_XmTimeout);
    if (0 == rv) {
        CONCOM(printk("RECV REMOTE CHAR TIMEOUT g_XmTimeout %d\n", g_XmTimeout));
        return re_acs;
    }
    CONCOM(printk("RECV REMOTE CHAR [%c][%02x]\n", res_c, res_c));
    switch (res_c) {
        case ACK:
            re_acs = AC_ACCEPT;
            break;

        case NAK:
            re_acs = AC_REJECT;
            break;

        case 'C':
        case 'G':
            re_acs = AC_DATA;
            break;

        case CAN:
            re_acs = AC_CANCEL;
            break;

        default:
            break;
    }
    return re_acs;
}

int XMIT_BUILD(char *packet, int xmitl)
{
    int i;
    char *p = packet;
    CONCOM(printk("XMIT_BUILD DATA LENGTH %0d\n",g_DataLen));

    if (xmitl < g_DataLen) {
        p = packet + PKGHDR + xmitl;// the ptr begin to fill dummy data
        for (i = 0; i < (g_DataLen - xmitl);) {
            p[i++] = 0x1A;
        }
        CONCOM(printk("TRANSFER FILE REACH TO END!\n"));
        g_State = X_EOT;
//     return sendChr(EOT);
    }

    packet[0] = (g_DataLen == MAXPACKET) ? STX : SOH;
    packet[1] = (char)g_PacketId;
    packet[2] = (char)(255 - g_PacketId);
    g_PacketId = (g_PacketId + 1) % 256;
// ++g_PacketId;

    if (g_IsUseCrc) {
        i = CalCrc(packet + PKGHDR, g_DataLen);
        packet[g_DataLen + PKGHDR] = (char)(i >> 8);
        packet[g_DataLen + PKGHDR + 1] = (char)(i & 0xff);
        CONCOM(printk("TRANSFER CRC %02x %02x\n", packet[g_DataLen + PKGHDR], packet[g_DataLen + PKGHDR + 1]));
    }
    else {
        packet[g_DataLen + PKGHDR] = (char)CalcChecksum(packet + PKGHDR, g_DataLen);
    }
    //return resendPacket();
    return 0;
}

int XMIT_XMIT(char* packet)
{
    CONCOM(printk("XMIT START.......!\n"));
    if (SendStr(packet, g_PktLen)) {
        Cancel();//can't send.....link crashed
        return AC_CANCEL;
    }
    CONCOM(printk("XMIT OVER .......!\n"));
    return 0;
}

int XMIT_INIT(int protocol)
{
    g_Protocol = protocol;

    g_PacketId = 1 ;
    g_PacketCount = 0 ;
    if (XMDM != g_Protocol) {
        g_IsUseCrc = 1;
        g_PktHdrLen = PKGHDR + CRCLEN;
        CONCOM(printk("CRC 1 HDRLEN %d\n", g_PktHdrLen));
    }
    switch (g_Protocol) {
        case XMDM :
            g_XmTimeout = INITTO;
            g_IsUseCrc = 0;
            g_PktHdrLen = PKGHDR + CSMLEN;
            break;

        case YMDM :
        case YMDMG :
            g_XmTimeout = INITTO;
            break;

        case XMDMCRC :
            g_XmTimeout = INITTO2;
            break;

        default:
            g_XmTimeout = INITTO;
    }

    return XmErrNone ;
}

unsigned int Read(/* [out] */ unsigned char *ebbData,
    /* [in]  */ unsigned int cbData,
    /* [out] */ unsigned int *pdwRead)
{
    int recv_pks, ac_st;
    char *pRxBuf;
    P_XYM_PKG pRxPKG = (P_XYM_PKG)ebbData;
    pRxBuf = pRxPKG->PacketData;
    ac_st = pRxPKG->ActionStatus;

    recv_pks = RECV(pRxBuf, ac_st);
    CONCOM(printk("RECV PKG STATUS %08x\n", recv_pks));

    pRxPKG->PacketStatus = recv_pks;
    pRxPKG->PacketDataLen = g_DataLen;
    *pdwRead = (sizeof(XYM_PKG));

    return XY_NOERR;
}

unsigned int Write(/* [out] */ unsigned char* ebbData,
    /* [in]  */ unsigned int cbData,
    /* [out] */ unsigned int *pdwWrt)
{
    char *pTxBuf;
    int ac_st, xmit_pks, xmit_dl;

    P_XYM_PKG pTxPKG = (P_XYM_PKG)ebbData;
    pTxBuf = pTxPKG->PacketData;
    xmit_pks = pTxPKG->PacketStatus;
    xmit_dl  = pTxPKG->PacketDataLen;
    g_DataLen = (xmit_dl > MINPACKET) ? MAXPACKET : MINPACKET;
    g_PktLen = g_DataLen + g_PktHdrLen;

    CONCOM(printk("XMIT PKG LEN %d\n", g_PktLen));
    ac_st = XMIT(pTxBuf, xmit_pks, xmit_dl);
    CONCOM(printk("XMIT PKG ACTION STATUS %08x\n", ac_st));

    pTxPKG->ActionStatus = ac_st;
    *pdwWrt = 12;
    return XY_NOERR;
}

unsigned int Control(
    /* [in] */ int nControlCode,
    /* [in] */ unsigned char *ebbInData,
    /* [out] */unsigned char *ebbOutData)
{
    unsigned int ec = XY_NOERR;
    int err;

    switch (nControlCode) {
        case 0x0A:
            err = RECV_INIT(YMDM);
            if (err) {
                ec = XY_E_FAIL;
            }
            break;

        case 0x0B:
            err = RECV_INIT(YMDMG);
            if (err) {
                ec = XY_E_FAIL;
            }
            break;

        case 0x1A:
            err = XMIT_INIT(YMDM);
            if (err) {
                ec = XY_E_FAIL;
            }
            break;

        case 0x1B:
            err = XMIT_INIT(YMDMG);
            if (err) {
                ec = XY_E_FAIL;
            }
            break;

        case AC_REJECT:
            err = RECV_REJECT();
            if (err) {
                ec = XY_E_FAIL;
            }
            break;

        case AC_ACCEPT:
            err = RECV_ACCEPT();
            if (err) {
                ec = XY_E_FAIL;
            }
            break;

        case AC_CANCEL:
            err = Cancel();
            if (err) {
                ec = XY_E_FAIL;
            }
            break;

        case AC_YMD1STPK:
            g_PacketCount = 1;
            RECV_ACCEPT();
            if (g_Protocol != YMDMG) {
                err =Flush('C');
            }
            else {
                err = Flush('G');
            }
            if (err) {
                ec = XY_E_FAIL;
            }
            break;

        default:
            break;
    }
    return ec;
}
